package client

import (
	"addsvc"
	"addsvc/internal/endpoint"
	"addsvc/internal/utils/method"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"
	"strings"

	"net/http"
	"net/url"

	kitEndpoint "github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/sd"
	"github.com/go-kit/kit/sd/etcdv3"
	kitHttp "github.com/go-kit/kit/transport/http"
	"github.com/go-kit/log"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
)

func HTTP(prefix string, discoverys []string, tracer trace.Tracer, logger log.Logger) (addsvc.Service, error) {
	client, err := etcdv3.NewClient(context.Background(), discoverys, etcdv3.ClientOptions{})
	if err != nil {
		return nil, err
	}

	sum := makeEndpoint(
		prefix, client,
		method.Sum,
		logger, tracer,
		makeHTTPFactory(method.Sum, tracer, logger),
	)
	concat := makeEndpoint(
		prefix, client,
		method.Concat,
		logger, tracer,
		makeHTTPFactory(method.Concat, tracer, logger),
	)
	return &endpoint.Endpoints{
		SumPoint:    sum,
		ConcatPoint: concat,
	}, nil
}

func makeHTTPFactory(method method.Method, tracer trace.Tracer, logger log.Logger) sd.Factory {
	encodeRequest, decodeResponse := getHTTPFunc(method)

	return func(instance string) (kitEndpoint.Endpoint, io.Closer, error) {
		if !strings.HasPrefix(instance, "http") {
			instance = "http://" + instance
		}
		tgt, err := url.Parse(instance)
		if err != nil {
			return nil, nil, err
		}
		tgt.Path = "/" + strings.ToLower(string(method))

		end := kitHttp.NewClient(
			"POST",
			tgt,
			encodeRequest,
			decodeResponse,
			kitHttp.ClientBefore(ContextToHTTP(tracer, logger)),
		).Endpoint()
		return end, nil, nil
	}
}

func ContextToHTTP(tracer trace.Tracer, logger log.Logger) kitHttp.RequestFunc {
	return func(ctx context.Context, r *http.Request) context.Context {
		propagator := otel.GetTextMapPropagator()
		propagator.Inject(ctx, propagation.HeaderCarrier(r.Header))
		return ctx
	}
}

func getHTTPFunc(m method.Method) (kitHttp.EncodeRequestFunc, kitHttp.DecodeResponseFunc) {
	switch m {
	case method.Sum:
		return encodeHTTPRequest, decodeHTTPSumResponse
	case method.Concat:
		return encodeHTTPRequest, decodeHTTPConcatResponse
	}
	return nil, nil
}

// decodeHTTPSumResponse is a transport/http.DecodeResponseFunc that decodes a
// JSON-encoded sum response from the HTTP response body. If the response has a
// non-200 status code, we will interpret that as an error and attempt to decode
// the specific error message from the response body. Primarily useful in a
// client.
func decodeHTTPSumResponse(_ context.Context, r *http.Response) (any, error) {
	if r.StatusCode != http.StatusOK {
		return nil, errors.New(r.Status)
	}
	var resp endpoint.SumResponse
	err := json.NewDecoder(r.Body).Decode(&resp)
	return resp, err
}

// decodeHTTPConcatResponse is a transport/http.DecodeResponseFunc that decodes
// a JSON-encoded concat response from the HTTP response body. If the response
// has a non-200 status code, we will interpret that as an error and attempt to
// decode the specific error message from the response body. Primarily useful in
// a client.
func decodeHTTPConcatResponse(_ context.Context, r *http.Response) (any, error) {
	if r.StatusCode != http.StatusOK {
		return nil, errors.New(r.Status)
	}
	var resp endpoint.ConcatResponse
	err := json.NewDecoder(r.Body).Decode(&resp)
	return resp, err
}

// encodeHTTPRequest is a transport/http.EncodeRequestFunc that
// JSON-encodes any request to the request body. Primarily useful in a client.
func encodeHTTPRequest(_ context.Context, r *http.Request, request any) error {
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(request); err != nil {
		return err
	}
	r.Body = io.NopCloser(&buf)
	return nil
}
